<html>
<head><meta charset="utf-8"><title>A case for CancellationTokens · wg-async-foundations · Zulip Chat Archive</title></head>
<h2>Stream: <a href="https://rust-lang.github.io/zulip_archive/stream/187312-wg-async-foundations/index.html">wg-async-foundations</a></h2>
<h3>Topic: <a href="https://rust-lang.github.io/zulip_archive/stream/187312-wg-async-foundations/topic/A.20case.20for.20CancellationTokens.html">A case for CancellationTokens</a></h3>

<hr>

<base href="https://rust-lang.zulipchat.com">

<head><link href="https://rust-lang.github.io/zulip_archive/style.css" rel="stylesheet"></head>

<a name="248390227"></a>
<h4><a href="https://rust-lang.zulipchat.com#narrow/stream/187312-wg-async-foundations/topic/A%20case%20for%20CancellationTokens/near/248390227" class="zl"><img src="https://rust-lang.github.io/zulip_archive/assets/img/zulip.svg" alt="view this post on Zulip" style="width:20px;height:20px;"></a> Matthias247 <a href="https://rust-lang.github.io/zulip_archive/stream/187312-wg-async-foundations/topic/A.20case.20for.20CancellationTokens.html#248390227">(Aug 04 2021 at 18:39)</a>:</h4>
<p>I finally found some nights to for writing up some thoughts and comparisons around how graceful cancellation support can be added to shiny new async ecosytem (as well as to the existing one, or even to synchronous functions).</p>
<p>You can find it at <a href="https://gist.github.com/Matthias247/354941ebcc4d2270d07ff0c6bf066c64">https://gist.github.com/Matthias247/354941ebcc4d2270d07ff0c6bf066c64</a></p>
<p>The doc might be rough in a few places (time, other responsibilities...), but I hope it gives some ideas around what needs to be considered, and why some variant of a <code>CancellationToken/StopToken</code> are would be a good tool to standardize in order to each the desired goals.</p>
<p><span class="user-mention" data-user-id="116009">@nikomatsakis</span> This probably fits somewhere into sections <a href="https://nikomatsakis.github.io/wg-async-foundations/vision/shiny_future/users_manual.html#cancellation">https://nikomatsakis.github.io/wg-async-foundations/vision/shiny_future/users_manual.html#cancellation</a> or <a href="https://nikomatsakis.github.io/wg-async-foundations/vision/deliverables/borrowed_data_and_cancellation.html#cancellation">https://nikomatsakis.github.io/wg-async-foundations/vision/deliverables/borrowed_data_and_cancellation.html#cancellation</a></p>
<p>I think the latter section should  point out cancellation support as a dedicated deliverable, since it's a larger task on its own and can live independent of the <code>scope</code> API.</p>



<a name="248411994"></a>
<h4><a href="https://rust-lang.zulipchat.com#narrow/stream/187312-wg-async-foundations/topic/A%20case%20for%20CancellationTokens/near/248411994" class="zl"><img src="https://rust-lang.github.io/zulip_archive/assets/img/zulip.svg" alt="view this post on Zulip" style="width:20px;height:20px;"></a> Kestrer <a href="https://rust-lang.github.io/zulip_archive/stream/187312-wg-async-foundations/topic/A.20case.20for.20CancellationTokens.html#248411994">(Aug 04 2021 at 21:34)</a>:</h4>
<p>This article doesn't mention the <code>AsyncDrop</code> approach. It is basically identically to the <code>poll_cancel</code> approach, but slightly more powerful because completion is guaranteed from type creation, not just first poll. This means that the <code>scope</code> API is unnecessary since all its power can be gained from a <code>spawn</code> function that has no lifetime bounds. I will be responding from the perspective of that approach because I do like it the most, but it is fairly similar to options A) and B).</p>
<blockquote>
<p>The CancellationToken approach requires less code to be touched to add graceful cancellation support.</p>
</blockquote>
<p>This is true of the <code>poll_cancel</code> and <code>AsyncDrop</code> approaches also, which implicitly forward cancellation.</p>
<p>In response to the multithreading issues, runtimes would support cross-thread cancellation of tasks by having the <code>poll_cancel</code>/<code>AsyncDrop</code> method of the task handle set the "cancel me" bit and then await the task's actual cancellation. Under AsyncDrop, the <code>scope</code> function (which is useless now but anyway) could be implemented like so:</p>
<div class="codehilite" data-code-language="Rust"><pre><span></span><code><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">scope</span><span class="o">&lt;</span><span class="n">F</span><span class="p">,</span><span class="w"> </span><span class="n">R</span><span class="o">&gt;</span><span class="p">(</span><span class="n">scope_fn</span>: <span class="nc">F</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">R</span><span class="w"></span>
<span class="k">where</span><span class="w"> </span><span class="n">F</span>: <span class="nc">AsyncFnOnce</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nc">R</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">scope_state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ScopeState</span>::<span class="n">new</span><span class="p">();</span><span class="w"></span>
<span class="w">    </span><span class="n">scope_fn</span><span class="p">(</span><span class="n">scope_state</span><span class="p">.</span><span class="n">handle</span><span class="p">()).</span><span class="k">await</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>The automatically implemented asynchronous destructor of ScopeState would just cancel all the async tasks automatically when the ScopeState goes out of scope.</p>
<p>"time-to-time" cancellation of sync code can also be done within all of the other approaches (this code would work in <code>poll_cancel</code>, <code>AsyncDrop</code> and maybe <code>request_cancellation</code>):</p>
<div class="codehilite" data-code-language="Rust"><pre><span></span><code><span class="k">impl</span><span class="w"> </span><span class="n">FileClient</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="k">fn</span> <span class="nf">write_all</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">buffer</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="kt">u8</span><span class="p">])</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span><span class="w"> </span><span class="n">Error</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">cancel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">AtomicBool</span>::<span class="n">new</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"></span>
<span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">task</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">spawn_blocking</span><span class="p">(</span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">offset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w">            </span><span class="k">while</span><span class="w"> </span><span class="n">offset</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">buffer</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">                </span><span class="k">if</span><span class="w"> </span><span class="n">cancel</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">Relaxed</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w">                </span><span class="n">offset</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">file</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="o">&amp;</span><span class="n">buffer</span><span class="p">[</span><span class="n">offset</span><span class="o">..</span><span class="p">])</span><span class="o">?</span><span class="p">;</span><span class="w"></span>
<span class="w">            </span><span class="p">}</span><span class="w"></span>
<span class="w">            </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="w">        </span><span class="p">});</span><span class="w"></span>
<span class="w">        </span><span class="n">defer</span><span class="o">!</span><span class="p">(</span><span class="n">cancel</span><span class="p">.</span><span class="n">store</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="n">Relaxed</span><span class="p">));</span><span class="w"></span>
<span class="w">        </span><span class="n">task</span><span class="p">.</span><span class="k">await</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p><code>CancellationToken</code> does definitely provide an advantage in being directly interopable with cancellable synchronous operations. However, in my experience this is rare - 99% of I/O will be through inherently cancellable asynchronous APIs (especially once completion futures gives io_uring/IOCP support) and if that 1% does really need to be cancelled with a low latency it is always possible to build CancellationToken-like things on top of another method, such as a <code>poll_cancel</code> that calls <code>CancelSynchronousIo</code>.</p>
<blockquote>
<p>its application is already very well understood due to widespread industry usage.</p>
</blockquote>
<p>As someone who is not in the industry, cancellation tokens were intially a lot more confusing to me than the other approaches. I think this is because at the moment it seems to be just a magic black box that deals with cancellation - I have no well-defined concept of a "token" I can map it on to. Maybe having a properly designed API proposal would help with this. In contrast, <code>poll_cancel</code> and <code>AsyncDrop</code> feel a lot more natural to me, they are just the asynchronous versions of the current future drop mechanism.</p>



<hr><p>Last updated: Aug 07 2021 at 22:04 UTC</p>
</html>